#ifndef RASTERIZER_HPP
#define RASTERIZER_HPP

#include "StbImage.hpp"
#include <iostream>
#include <vector>
#include <Eigen/Eigen>

using vec2 = Eigen::Vector2f;
using vec3 = Eigen::Vector3f;
using vec4 = Eigen::Vector4f;

using vec2i = Eigen::Vector2i;
using vec3i = Eigen::Vector3i;
using vec4i = Eigen::Vector4i;

using std::vector;
using std::array;
using std::string;
using std::min;
using std::max;

/**
 *@brief A triangle primitive.
*/
struct Triangle {
    vector<vec4> vertecis;
    vector<vec3> colors;

    Triangle(const vector<vec4>& vertecis, const vector<vec3>& colors) :vertecis(vertecis), colors(colors) {
        auto v1 = vertecis[1] - vertecis[0];
        auto v2 = vertecis[2] - vertecis[0];

        if (v1[0] * v2[1] - v1[1] * v2[0] > 0) {
            this->vertecis[1] = vertecis[2];
            this->colors[1] = colors[2];
            this->vertecis[2] = vertecis[1];
            this->colors[2] = colors[1];
        }
    }
};

class Rasterizer {
private:
    vector<float> zbuffer;
    StbImage frame;
    int w, h, n;// Width, height, pipe number.

public:
    Rasterizer(int width, int height, int n) :zbuffer(width* height, 114514), frame(width, height, n) {
        w = width;
        h = height;
        this->n = n;
    }

    /**
     * @brief Output the rendergraph to ''fullNmae'.
     *
    */
    void write(const string& fullName) {
        frame.write(fullName);
    }

    /**
     *@brief Draw a single triangle which has been cliped on the frame.
     *@param antiAliasingLevel How many pieces will be per pixel.
    */
    void drawTriangle(const Triangle& triangle, int antiAliasingLevel = 1) {
        int l, r, b, t;
        vector<Eigen::Vector2i> itri;
        array<vec3i, 3> colors;
        vector<vec2i> tmp;
        for (auto& i : triangle.vertecis) {
            tmp.emplace_back(toViewportCoordinate(i));
        }
        clipTriangle(triangle, itri);

        int num = itri.size() - 1;
        vec3 bary, colorRaw(0, 0, 0);
        vec3i color;
        float depth;
        for (int k = 1; k < num; ++k) {
            l = min(itri[0][0], min(itri[k][0], itri[k + 1][0]));
            r = max(itri[0][0], max(itri[k][0], itri[k + 1][0]));
            b = min(itri[0][1], min(itri[k][1], itri[k + 1][1]));
            t = max(itri[0][1], max(itri[k][1], itri[k + 1][1]));

            float inc = 1.0 / antiAliasingLevel;
            for (int i = l; i <= r; ++i) {
                for (int j = b; j <= t; ++j) {
                    if (isInTriangle(i, j, antiAliasingLevel, { 0, k, k + 1 }, itri)) {
                        for (int d1 = 0; d1 < antiAliasingLevel; ++d1) {
                            for (int d2 = 0; d2 < antiAliasingLevel; ++d2) {
                                float x = i + d1 * inc, y = j + d2 * inc;
                                toBarycentricCoordinate(vec2{ x,y }, bary, tmp);
                                if (bary[0]>=0 && bary[1]>=0 && bary[2]>=0) {
                                    for (int o = 0; o < 3; ++o) {
                                        colorRaw += bary[o] * triangle.colors[o];
                                    }
                                }
                            }
                        }
                        colorRaw /= antiAliasingLevel * antiAliasingLevel;
                        color << colorRaw[0] * 255, colorRaw[1] * 255, colorRaw[2] * 255;
                        colorRaw << 0, 0, 0;
                        depth =
                            bary[0] * triangle.vertecis[0].z() +
                            bary[1] * triangle.vertecis[1].z() +
                            bary[2] * triangle.vertecis[2].z();
                        if (zbuffer[toBufferIndex(i, j)] > depth) {
                            frame.setColor(i, j, color);
                            zbuffer[toBufferIndex(i, j)] = depth;
                        }
                    }
                }
            }
        }
    }

    /**
     *@brief Draw a series of triangles on the frame.
    */
    void drawTriangles(const vector<Triangle>& triangles, int antiAliasingLevel = 1) {
        for (auto& triangle : triangles) {
            drawTriangle(triangle, antiAliasingLevel);
        }
    }

private:
    /**
     * @brief Whether the point (x,y) is in triangle.
     * @param indecis The indecis of the vertecis in 'tri' that we use as a triangle.
     * @param tri A series of point.
    */
    bool isInTriangle(int x, int y, const std::array<int, 3>& indecis, vector<Eigen::Vector2i>& tri) {
        if (indecis.size() != 3 || tri.size() < 3) {
            std::cerr << "Error[func isInTriangle]: This is not a triangle" << endl;
            exit(114514);
        }
        Eigen::Vector2i v1, v2;
        int i1, i2;
        for (int ix = 0; ix < 3; ++ix) {
            i1 = indecis[ix];
            i2 = indecis[(ix + 1) % 3];
            v1 << tri[i2][0] - tri[i1][0], tri[i2][1] - tri[i1][1];
            v2 << x - tri[i1][0], y - tri[i1][1];
            if (v2[0] * v1[1] - v2[1] * v1[0] < 0) {
                return false;
            }
        }
        return true;
    }
    /**
    * @brief Whether the point (x,y) is in triangle, use to anti-aliasing.
    * @param indecis The indecis of the vertecis in 'tri' that we use as a triangle.
    * @param tri A series of point.
   */
    bool isInTriangle(int x, int y, int div, const std::array<int, 3>& indecis, vector<Eigen::Vector2i>& tri) {
        if (indecis.size() != 3 || tri.size() < 3) {
            std::cerr << "Error[func isInTriangle]: This is not a triangle" << endl;
            exit(114514);
        }
        Eigen::Vector2i v1, v2;
        int i1, i2;
        float inc = 1.0 / div;
        for (int d1 = 0; d1 < div; ++d1) {
            for (int d2 = 0; d2 < div; ++d2) {
                for (int ix = 0; ix < 3; ++ix) {
                    i1 = indecis[ix];
                    i2 = indecis[(ix + 1) % 3];
                    v1 << tri[i2][0] - tri[i1][0], tri[i2][1] - tri[i1][1];
                    v2 << x + d1 * inc - tri[i1][0], y + d2* inc - tri[i1][1];
                    if (v2[0] * v1[1] - v2[1] * v1[0] < 0) {
                        goto out;
                    }
                }
                return true;
            out:continue;
            }
        }
        return false;
    }
    /**
     * @brief Get the Barycentric coordinate location of point 'v'.
     * @param v The point.
     * @param ans The output result .
     * @param tri 3 vertecis, represent the triangle.
    */
    inline void toBarycentricCoordinate(const vec2i& v, vec3& ans, const vector<vec2i>& tri)const {
        if (tri.size() != 3) {
            std::cerr << "Error[func toBarycentricCoordinate]: This is not a triangle" << endl;
            exit(114514);
        }
        ans[0] = float(-(v.x() - tri[1].x()) * (tri[2].y() - tri[1].y()) + (v.y() - tri[1].y()) * (tri[2].x() - tri[1].x())) /
            (-(tri[0].x() - tri[1].x()) * (tri[2].y() - tri[1].y()) + (tri[0].y() - tri[1].y()) * (tri[2].x() - tri[1].x()));
        ans[1] = float(-(v.x() - tri[2].x()) * (tri[0].y() - tri[2].y()) + (v.y() - tri[2].y()) * (tri[0].x() - tri[2].x())) /
            (-(tri[1].x() - tri[2].x()) * (tri[0].y() - tri[2].y()) + (tri[1].y() - tri[2].y()) * (tri[0].x() - tri[2].x()));
        ans[2] = 1 - ans[0] - ans[1];
    }
    /**
     * @brief Get the Barycentric coordinate location of point 'v'.
     * @param v The point.
     * @param ans The output result .
     * @param tri 3 vertecis, represent the triangle.
    */
    inline void toBarycentricCoordinate(const vec2& v, vec3& ans, const vector<vec2i>& tri)const {
        if (tri.size() != 3) {
            std::cerr << "Error[func toBarycentricCoordinate]: This is not a triangle" << endl;
            exit(114514);
        }
        ans[0] = float(-(v.x() - tri[1].x()) * (tri[2].y() - tri[1].y()) + (v.y() - tri[1].y()) * (tri[2].x() - tri[1].x())) /
            (-(tri[0].x() - tri[1].x()) * (tri[2].y() - tri[1].y()) + (tri[0].y() - tri[1].y()) * (tri[2].x() - tri[1].x()));
        ans[1] = float(-(v.x() - tri[2].x()) * (tri[0].y() - tri[2].y()) + (v.y() - tri[2].y()) * (tri[0].x() - tri[2].x())) /
            (-(tri[1].x() - tri[2].x()) * (tri[0].y() - tri[2].y()) + (tri[1].y() - tri[2].y()) * (tri[0].x() - tri[2].x()));
        ans[2] = 1 - ans[0] - ans[1];
    }
    inline vec2i toViewportCoordinate(const vec4& v)const {
        return vec2i(v[0] * w, v[1] * h);
    }

    inline int toBufferIndex(int x, int y)const {
        return y * w + x;
    }

    /**
     *@brief Clip a  triangle primitive to fit the screen.
    */
    void clipTriangle(const Triangle& tri, vector<vec2i>& output) {
        output.clear();

        vector<vec2i> tmp;
        output.emplace_back(toViewportCoordinate(tri.vertecis[0]));
        output.emplace_back(toViewportCoordinate(tri.vertecis[1]));
        output.emplace_back(toViewportCoordinate(tri.vertecis[2]));

        const vec2i* v1, * v2;
        vec2i intersect;
        float k;
        // Left
        int num = (int)output.size();
        for (int i = 0; i < num; ++i) {
            v1 = &output[i];
            v2 = &output[(i + 1) % num];
            k = float(v2->y() - v1->y()) / (v2->x() - v1->x());
            intersect << 0, -v1->x() * k + v1->y();
            if (v1->x() >= 0 && v2->x() >= 0) {
                tmp.emplace_back(*v2);
            }
            else if (v1->x() < 0 && v2->x() >= 0) {
                tmp.emplace_back(intersect);
                tmp.emplace_back(*v2);
            }
            else if (v1->x() >= 0 && v2->x() < 0) {
                tmp.emplace_back(intersect);
            }
            else {}
        }

        // right
        output.clear();
        num = (int)tmp.size();
        for (int i = 0; i < num; ++i) {
            v1 = &tmp[i];
            v2 = &tmp[(i + 1) % num];
            k = float(v2->y() - v1->y()) / (v2->x() - v1->x());
            intersect << w - 1, (w - 1 - v1->x())* k + v1->y();
            if (v1->x() < w && v2->x() < w) {
                output.emplace_back(*v2);
            }
            else if (v1->x() >= w && v2->x() < w) {
                output.emplace_back(intersect);
                output.emplace_back(*v2);
            }
            else if (v1->x() < w && v2->x() >= w) {
                output.emplace_back(intersect);
            }
            else {}
        }
        // bottom
        tmp.clear();
        num = output.size();
        for (int i = 0; i < num; ++i) {
            v1 = &output[i];
            v2 = &output[(i + 1) % num];
            k = (v2->x() - v1->x()) / float(v2->y() - v1->y());
            intersect << -v1->y() * k + v1->x(), 0;
            if (v1->y() >= 0 && v2->y() >= 0) {
                tmp.emplace_back(*v2);
            }
            else if (v1->y() < 0 && v2->y() >= 0) {
                tmp.emplace_back(intersect);
                tmp.emplace_back(*v2);
            }
            else if (v1->y() >= 0 && v2->y() < 0) {
                tmp.emplace_back(intersect);
            }
            else {}
        }
        // top
        output.clear();
        num = (int)tmp.size();
        for (int i = 0; i < num; ++i) {
            v1 = &tmp[i];
            v2 = &tmp[(i + 1) % num];
            k = (v2->x() - v1->x()) / float(v2->y() - v1->y());
            intersect << (h - 1 - v1->y()) * k + v1->x(), h - 1;
            if (v1->y() < h && v2->y() < h) {
                output.emplace_back(*v2);
            }
            else if (v1->y() >= h && v2->y() < h) {
                output.emplace_back(intersect);
                output.emplace_back(*v2);
            }
            else if (v1->y() < h && v2->y() >= h) {
                output.emplace_back(intersect);
            }
            else {}
        }
    }
};

#endif // !RASTERIZER_HPP